package com.happiness.gateway.config.security;

import com.happiness.gateway.config.exception.RestAuthenticationEntryPoint;
import com.happiness.gateway.config.exception.RestfulAccessDeniedHandler;
import com.happiness.gateway.config.exception.UserLogoutHandler;
import com.happiness.gateway.config.jwt.JwtAuthenticationTokenFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsUtils;

import javax.annotation.Resource;

/**
 * SpringSecurity信息配置
 * */
@Configuration
@EnableWebSecurity// 开启Security
@EnableGlobalMethodSecurity(prePostEnabled = true)//保证post之前的注解可以使用
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private RestAuthenticationEntryPoint restAuthenticationEntryPoint;

    @Resource
    private RestfulAccessDeniedHandler restfulAccessDeniedHandler;

    @Resource
    private UserLogoutHandler userLogoutHandler;

    @Resource
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    //配置拦截
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // GET请求取消拦截
        String[] getUrls = {
                "/api/v1/**",
                // swagger拦截取消
                "/*.html","/favicon.ico","/**/*.html","/**/*.css","/**/*.js","/swagger-resources/**","/v3/api-docs/**"
        };
        http.cors().and()// spring security跨域
                .csrf().and()// 由于使用的是JWT，我们这里不需要csrf
                // 登录失效处理类
                .exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint).and()
                // 认证失败处理类
                .exceptionHandling().accessDeniedHandler(restfulAccessDeniedHandler).and()
                // 基于token，所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 过滤请求：
                //      anonymous（允许匿名用户访问必须待身份信息但是不校验权限）
                //      permitAll（无条件允许访问）
                .authorizeRequests()
                .antMatchers(HttpMethod.GET,getUrls)
                .permitAll()
                .antMatchers(HttpMethod.OPTIONS,"/**")//跨域请求会先进行一次options请求
                .permitAll()
                //让Spring security放行所有preflight request
                .requestMatchers(CorsUtils::isPreFlightRequest)
                .permitAll()
//                .antMatchers("/**")//测试时全部运行访问
//                .permitAll()
                .anyRequest()// 除上面外的所有请求全部需要鉴权认证
                .authenticated()
                // 退出登录方法
                .and().logout().logoutUrl("/logout").logoutSuccessHandler(userLogoutHandler)
                // 禁用缓存
                .and().headers().cacheControl();
                // 解决因为spring Security使用X-Frame-Options防止网页被Frame
                /*.and().headers().frameOptions().disable();*/
        // 添加JWT filter
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService())
                .passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
